package com.jahentao.patentQuery.web.controller;

import com.aliyuncs.exceptions.ClientException;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.jahentao.patentQuery.constant.Constants;
import com.jahentao.patentQuery.constant.EhCacheKeys;
import com.jahentao.patentQuery.constant.SysUri;
import com.jahentao.patentQuery.constant.VerificationCode;
import com.jahentao.patentQuery.model.SessionExample;
import com.jahentao.patentQuery.model.User;
import com.jahentao.patentQuery.model.UserExample;
import com.jahentao.patentQuery.model.bean.MessageCode;
import com.jahentao.patentQuery.model.bean.ResultBean;
import com.jahentao.patentQuery.model.page.QueryResult;
import com.jahentao.patentQuery.service.SessionService;
import com.jahentao.patentQuery.service.UserLoginService;
import com.jahentao.patentQuery.service.UserService;
import com.jahentao.patentQuery.util.IpUtil;
import com.jahentao.patentQuery.util.PasswordHelper;
import com.jahentao.patentQuery.util.mail.SendMailUtil;
import com.jahentao.patentQuery.util.sms.SmsUtil;
import com.jahentao.patentQuery.web.shiro.jcaptcha.JCaptcha;
import com.jahentao.patentQuery.web.shiro.spring.SpringCacheManagerWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.session.mgt.WebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 用户控制器
 * <p>其中的登录、注册功能，TODO 等到实现了OAth2协议后，就同样封装进{@link ThirdPartyLoginController}</p>
 * @see ThirdPartyLoginController
 * @author jahentao
 * @date 2018/6/17
 * @since 1.0
 */
@Controller
@RequestMapping(SysUri.USER)
public class UserController extends BaseController{

    @Autowired
    private UserLoginService userLoginService;
    @Autowired
    private UserService userService;
    @Autowired
    private SpringCacheManagerWrapper cacheManager;
    @Autowired
    private PasswordHelper passwordHelper;
    @Autowired
    private WebSessionManager webSessionManager;
    @Autowired
    private SessionService sessionService;

    // =============================页面跳转===================================

    @RequestMapping(value = "list")
    public String list() {
        return "admin/user/list";
    }

    @RequestMapping(value = "edit")
    public String edit() {
        return "admin/user/edit";
    }

    @RequestMapping(value = "add")
    public String add() {
        return "admin/user/add";
    }

    // =============================流程控制===================================

    @ResponseBody
    @RequestMapping(value = "getList",method = RequestMethod.POST)
    public ResultBean getList(@RequestParam(value = "pageNumber", defaultValue = "1")Integer pageNumber,
                              @RequestParam(value = "pageSize", defaultValue = "10")Integer pageSize) {
        ResultBean rb = new ResultBean();

        // 分页查询
        PageHelper.startPage(pageNumber, pageSize);
        final List<User> patentDemands = userService.getRegisteredUser();

        Page patentDemandsPage = (Page) patentDemands;
        QueryResult<User> queryResult = new QueryResult<User>();
        queryResult.setCurrentPage(pageNumber);
        queryResult.setShowNum(pageSize);
        queryResult.setPages(patentDemandsPage.getTotal(), pageSize);
        queryResult.setItems(patentDemands);

        // 返回结果
        rb.setData(queryResult);

        return rb;
    }

    /**
     * QQ 登录的openID验证
     * <p>OpenID是此网站上或应用中唯一对应用户身份的标识，网站或应用可将此ID进行存储，
     * 便于用户下次登录时辨识其身份，或将其与用户在网站上或应用中的原有账号进行绑定</p>
     * @param openId
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "validateOpenId", method = RequestMethod.POST)
    public ResultBean validateOpenId(String openId) {
        ResultBean rb = new ResultBean();

        com.jahentao.patentQuery.model.Session session = null;

        SessionExample sessionExample = new SessionExample();
        sessionExample.createCriteria().andOpenIdEqualTo(openId);
        List<com.jahentao.patentQuery.model.Session> sessions = sessionService.selectByExample(sessionExample);
        if (sessions == null || sessions.size() == 0) {
            rb.setMessage("QQ用户OpenId不合法");
            rb.setMessageCode(MessageCode.OPENID_INVALID);
            return rb;
        } else {
            session = sessions.get(0);

            // TODO 向腾讯服务器获取是否过期，或者自己计算是否过期

            if (!session.getValidated()) {
                rb.setMessage("QQ用户OpenId不合法");
                rb.setMessageCode(MessageCode.OPENID_INVALID);
                return rb;
            }
        }

        // 根据openId 获取用户，返回前台
        User user = userService.selectByKey(session.getUserId());

        Map<String, Object> data = new HashMap<>();
        data.put("user", user);

        rb.setMessage("QQ用户OpenId合法");
        rb.setMessageCode(MessageCode.OPENID_VALID);
        rb.setData(data);
        return rb;
    }

    /**
     * 处理用户登录请求
     */
    @ResponseBody
    @RequestMapping(value = "login", method = RequestMethod.POST)
    public ResultBean login(HttpServletRequest request, User user) {
        ResultBean rb = new ResultBean();

        if(user==null|| StringUtils.isBlank(user.getUsername())||StringUtils.isBlank(user.getPassword())) {
            rb = new ResultBean(false, MessageCode.PLEASE_LOGIN,"登录系统请填写用户名和密码","");
            return rb;
        }

        // 验证码处理
        ResultBean rbBean=capthchaProcess(request, user.getUsername(), user.getCaptcha());
        if(rbBean!=null) return rbBean;

        // rememberMe
        boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);

        // 登录主逻辑

        try {
            String host =  IpUtil.getRemoteIp(request); // 主机名

            userLoginService.loginWithUsername(user, rememberMe, host);

            // 登陆成功

            // 获取会话session
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession(false);
            // 验证会话是否过期，交给会话验证调度器 SessionValidationScheduler
            // 如果是在获取会话时验证了会话已过期，将抛出 InvalidSessionException
            if (session == null) {
                throw new InvalidSessionException();
            }

            // 跳转主页，要显示用户信息
            Map<String, Object> data = new HashMap<String, Object>(1);
            User loginedUser = userService.findByUsername(user.getUsername());
            // 删除敏感信息
            loginedUser.setPassword(null);
            loginedUser.setSalt(null);
            data.put("user", loginedUser);
            rb.setData(data);


        } catch (InvalidSessionException e) {
            rb = new ResultBean(false, MessageCode.INVALID_SESSION, "会话已过期，请重新登录", "");
        }catch (UnknownAccountException e) {
            rb = new ResultBean(false, MessageCode.SYS_NO_USER, "账号不存在!", "");
        } catch (IncorrectCredentialsException e) {
            rb = new ResultBean(false, MessageCode.SYS_NO_USER_AND_PASSWORD, "用户名或密码错误", "");
        } catch (ExcessiveAttemptsException e) {
            rb = new ResultBean(false, MessageCode.SYS_LOG_IN_TOO_MANY, "账户错误次数过多,暂时禁止登录!", "");
        } catch (Exception e) {
            rb = new ResultBean(false, MessageCode.ABNORMAL, "系统异常!");
        }
        return rb;
    }

    @ResponseBody
    @RequestMapping(value = "logout", method = RequestMethod.POST)
    public ResultBean logout() {
        ResultBean rb = new ResultBean();

        Subject subject = SecurityUtils.getSubject();
        String sessionId = subject.getSession().getId().toString();
        logger.debug("用户会话 {} 退出登录", sessionId);
        subject.logout();

        rb.setMessage("用户已退出登录");
        rb.setMessageCode(MessageCode.USER_LOGOUT);

//        Map<String, Object> data = new HashMap<String, Object>();
//        rb.setData(data);

        return rb;
    }

    /**
     * 获取验证码
     * <p>目前支持：短信验证码或邮箱验证码</p>
     * @param type 验证码类型
     * @param jcaptcha jcaptcha验证码
     */
    @ResponseBody
    @RequestMapping(value = "getVerificationCode", method = RequestMethod.POST)
    public ResultBean getVerificationCode(HttpServletRequest request, String type, String jcaptcha, User user) {
        ResultBean rb = new ResultBean();

        if (StringUtils.isBlank(jcaptcha)) {
            rb.setMessage("验证码为空");
            rb.setMessageCode(MessageCode.JCAPTCHA_EMPTY);
            return rb;
        }else {
            // 进行jcaptcha验证
            // 通过页面提交的验证码和存放在session中的验证码对比来进行校验
            boolean captchaResult= JCaptcha.validateResponse(request, jcaptcha);
            if (!captchaResult) {
                // jcaptcha错误
                rb.setMessage("验证码错误");
                rb.setMessageCode(MessageCode.JCAPTCHA_ERROR);
                return rb;
            }
        }

        // 给客户端发送验证码 主要
        // TODO 验证码次数发送过多的情况，要限制资源使用
        // 随机生成验证码
        String code = VerificationCode.generateCode();

        // 将session和验证码对应保存
        // 设置验证码过期时间，通过Ehcache缓存自动过期清除
        Cache<String, String> cache = cacheManager.getCache(EhCacheKeys.VERIFICATION_CODE_CACHE);
        String key = request.getSession().getId();
        // 先看看缓存中是否过期
        String sCode = cache.get(key);

        if (VerificationCode.SMS_TYPE.equals(type)) {
            logger.debug("给 {} 发送短信验证码", user.getTelephone());
            // 短信验证码
            try {
                // set code 已经设置过的验证码
                if (sCode == null) {
                    cache.put(key, code);
                    // 发送
                    SmsUtil.sendSms(user.getTelephone(), code);
                } else {
                    // 发送未过期的验证码
                    SmsUtil.sendSms(user.getTelephone(), sCode);
                }

            } catch (ClientException e) {
                logger.debug("发送给 {} 的短信验证码发送失败", user.getTelephone());
                rb.setMessage("短信验证码发送失败，请更换手机号或更换验证方式");
                rb.setMessageCode(MessageCode.CAPTCHA_SMS_SEND_FAILURE);
                return rb;
            }

        }else if (VerificationCode.MAIL_TYPE.equals(type)) {
            // 邮件验证码
            logger.debug("给 {} 发送验证码邮件", user.getEmail());
            try {
                if (sCode == null) {
                    cache.put(key, code);
                    // 发送
                    SendMailUtil.sendVerificationCodeInEmail(user.getEmail(), code);
                } else {
                    // 发送未过期的验证码
                    SendMailUtil.sendVerificationCodeInEmail(user.getEmail(), sCode);
                }

            } catch (ClientException e) {
                logger.debug("给 {} 的验证码邮件发送失败", user.getEmail());
                rb.setMessage("邮件发送失败，请更换邮箱或更换验证方式");
                rb.setMessageCode(MessageCode.CAPTCHA_EMAIL_SEND_FAILURE);
                return rb;
            }
        }
        return rb;
    }

    @ResponseBody
    @RequestMapping(value = "register", method = RequestMethod.POST)
    public ResultBean register(HttpServletRequest request, User user) {
        ResultBean rb = new ResultBean();

        // 注册的时候暂无用户名，要等注册完成后，填写用户信息修改。否则，若用户名位空，以telephone为用户名
        if(user==null || StringUtils.isBlank(user.getPassword())
                || StringUtils.isBlank(user.getCaptcha()) || StringUtils.isBlank(user.getEmail())) {
            rb = new ResultBean(false, MessageCode.PLEASE_REGISTER,"请填写完整的信息，注册账户","");
            return rb;
        }

        // 短信或邮件验证码处理
        Cache<String, String> cache = cacheManager.getCache(EhCacheKeys.VERIFICATION_CODE_CACHE);
        String key = request.getSession().getId();
        String code = cache.get(key);
        if (!code.equals(user.getCaptcha())) {
            // 验证码不正确 或已过期
            rb.setMessageCode(MessageCode.CAPTCHA_VERIFICATION_FAILURE);
            rb.setMessage("验证码错误或已过期");
            return rb;
        }

        // 注册主逻辑
        //判断用户是否已存在
        UserExample userExample = new UserExample();
        userExample.createCriteria().andTelephoneEqualTo(user.getTelephone());
        List<User> users = userService.selectByExample(userExample);
        if (users != null && users.size() == 1) {
            // 以手机号为唯一标识
            if (user.getEmail().equals(users.get(0).getEmail())) {
                // TODO 判断邮箱是否已经验证
                // 要拓展字段，邮箱是否验证，手机号是否验证，...，其他联系方式是否验证
            }
            rb.setMessageCode(MessageCode.REGISTER_USER_EXIST);
            rb.setMessage("用户已存在，请换一个手机号注册");
            return rb;
        }

        // 注册之后是“用户”
        user.setUtype(String.valueOf(User.UserTypeEnum.USER));
        user.setUsername(user.getTelephone());
        user.setLocked((byte) 0);
        // 注册之后，得完善信息，才是合法的
        user.setValidated((byte)0);
        // 密码加密
        passwordHelper.encryptPassword(user);
        int saved = userService.save(user);
        if (saved <=0) {
            rb.setMessage("系统异常，用户注册失败");
            rb.setMessageCode(MessageCode.REGISTER_FAILURE);
            return rb;
        } else {
            // 用户注册成功，验证码立即失效
            cache.remove(key);
        }

        return rb;
    }

    /**
     * 验证码处理流程
     * <p>当输入次数超过{@value Constants#USER_LOGIN_COUNT_NEED_CAPTCHA}时，需要输入验证码</p>
     * @param request
     * @param username
     * @param captcha
     * @return
     */
    private ResultBean capthchaProcess(HttpServletRequest request, String username, String captcha) {
        ResultBean rb = null;


        Cache<String, AtomicInteger> cache= cacheManager.getCache(EhCacheKeys.LOGIN_LOG_CACHE);
        AtomicInteger loginCount=cache.get(username);

        // 是否需要验证码
        if(loginCount!=null && loginCount.get() >= Constants.USER_LOGIN_COUNT_NEED_CAPTCHA) {
            if(StringUtils.isBlank(captcha)){
                rb = new ResultBean(false,MessageCode.PLASS_CAPTCHA,"登录失败次数过多，请输入验证码","");
                return rb;
            }

            boolean captchaResult= JCaptcha.validateResponse(request, captcha);

            if(!captchaResult) {
                System.out.println(captchaResult);
                rb = new ResultBean(false,MessageCode.CAPTCHA_ERROR,"验证码不正确","");
                return rb;
            }
        }

        return rb;
    }

}
